This notebook reports on several approaches to selecting tree stems
for individual-scale species classification, uniting Kueppers, Worsham
et al. forest inventory data with Falco et al. species classification
map derived from the 2018 NEON spectroscopy mission. The approaches
range from least to most conservative, that is, from preserving all
trees to removing all but those in the uppermost canopy. Maps, agreement
statistics, and agreement figures are shown for each approach.
Pipeline
- Align geolocated tree stem objects to classification raster.
- A. Define a buffer of radius \(r\)
around each tree stem object, approximating crown area. B. Segment trees
using marker-controlled watershed segmentation algorithm to determine
crown area.
- Filter crown objects according to one of 4 specified
procedures.
- Plot a map of crown objects and classification data at an example
site.
- For each crown object, extract intersecting raster values by
majority vote.
- Compute accuracy statistics.
- Generate agreement figures.
Approaches
- Height-dependent buffer and no filtering
- Height-dependent buffer and filter on 90th percentile height or
intersecting crowns < 90th percentile height
- Height-dependent buffer and filter on 90th percentile height or
non-intersecting crowns
- Height-dependent buffer and filter on 90th percentile height
- Static 3m buffer and filter on 90th percentile height
- Segment tree-crown polygons and no filtering
- Segment tree polygons and filter on 90th percentile height
1. Height dependent buffer and no filtering
The least conservative approach. Keep every LiDAR-detected tree. Set
a height-dependent buffer around each object and attempt to extract
species within the buffer using majority vote.
Map

Confusion matrix
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
321 |
111 |
701 |
0 |
0 |
| PICO |
5 |
158 |
12 |
0 |
0 |
| PIEN |
95 |
35 |
486 |
0 |
0 |
| POTR |
2 |
5 |
8 |
0 |
0 |
| UNKN |
0 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.24 |
0.5 |
0.48 |
0.52 |
| Class |
Sensitivity |
Specificity |
Pos.Pred.Value |
Neg.Pred.Value |
Precision |
Recall |
F1 |
Prevalence |
Detection.Rate |
Detection.Prevalence |
Balanced.Accuracy |
| Class: ABLA |
0.76 |
0.46 |
0.28 |
0.87 |
0.28 |
0.76 |
0.41 |
0.22 |
0.17 |
0.58 |
0.61 |
| Class: PICO |
0.51 |
0.99 |
0.90 |
0.91 |
0.90 |
0.51 |
0.65 |
0.16 |
0.08 |
0.09 |
0.75 |
| Class: PIEN |
0.40 |
0.82 |
0.79 |
0.46 |
0.79 |
0.40 |
0.53 |
0.62 |
0.25 |
0.32 |
0.61 |
| Class: POTR |
NA |
0.99 |
NA |
NA |
0.00 |
NA |
NA |
0.00 |
0.00 |
0.01 |
NA |
| Class: UNKN |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
Agreement: kernel density by species and height

Agreement: frequency by species and size bin

2. Height-dependent buffer and filter on 90th percentile height or
intersecting crowns < 90th percentile height
Keep any tree whose height is in the 90th percentile or higher. For
all other trees, only keep them if (a) they don’t intersect another
tree’s crown area or (b) if the tree whose area they intersect is not in
the 90th percentile height.
- Apply a height-dependent buffer of radius \(r=0.03H + 0.5\), around every tree.
- For each tree \(t_i\):
- if: \(H \ge H_{90pctl}\), keep
\(t_i\).
- else:
- Test whether crown area sits entirely within another tree crown
area.
- if True: discard \(t_i\)
- if False:
- Test whether crown area of \(t_i\)
intersects any other tree crown area.
- if True: test whether any of the trees \(t_i\) intersects has \(H \ge H_{90pctl}\)
- if True: discard \(t_i\)
- if False: keep \(t_i\)
- if False: keep \(t_i\)
# Create canopy filter
canopy.filter.a <- unlist(lapply(1:nrow(stem.buff), \(i) {
if(stem.buff[i,]$Zpred < quantile(stem.buff$Zpred, 0.9)) {
if(length(stem.within[[i]])<=1) {
tst <- stem.buff[i,]$Zpred > 0.9 * stem.buff[stem.overlap[[i]],]$Zpred &
!any(stem.buff[stem.overlap[[i]],]$Zpred >= quantile(stem.buff$Zpred, 0.9))
tst <- prod(tst)
} else {
tst <- 0 }
} else {
tst <- 1
}
as.logical(tst)
}
))
# Apply canopy filter to buffered stems
stem.filt.a <- stem.buff[canopy.filter.a,]
Map

Confusion matrix
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
93 |
39 |
238 |
0 |
0 |
| PICO |
2 |
75 |
5 |
0 |
0 |
| PIEN |
33 |
5 |
251 |
0 |
0 |
| POTR |
1 |
1 |
5 |
0 |
0 |
| UNKN |
0 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.31 |
0.56 |
0.52 |
0.6 |
| Class |
Sensitivity |
Specificity |
Pos.Pred.Value |
Neg.Pred.Value |
Precision |
Recall |
F1 |
Prevalence |
Detection.Rate |
Detection.Prevalence |
Balanced.Accuracy |
| Class: ABLA |
0.72 |
0.55 |
0.25 |
0.90 |
0.25 |
0.72 |
0.37 |
0.17 |
0.12 |
0.49 |
0.64 |
| Class: PICO |
0.62 |
0.99 |
0.91 |
0.93 |
0.91 |
0.62 |
0.74 |
0.16 |
0.10 |
0.11 |
0.81 |
| Class: PIEN |
0.50 |
0.85 |
0.87 |
0.46 |
0.87 |
0.50 |
0.64 |
0.67 |
0.34 |
0.39 |
0.68 |
| Class: POTR |
NA |
0.99 |
NA |
NA |
0.00 |
NA |
NA |
0.00 |
0.00 |
0.01 |
NA |
| Class: UNKN |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
Agreement: kernel density by species and height

Agreement: frequency by species and size bin

3. Height-dependent buffer and filter on 90th percentile height or
non-intersecting crowns
Keep any tree whose height is in the 90th percentile or higher. For
all other trees, only keep them if (a) they don’t intersect another
tree’s crown area.
- Apply a height-dependent buffer of radius \(r=0.042H + 0.675\), around every tree.
- For each tree \(t_i\):
- if: \(H \ge H_{90pctl}\), keep
\(t_i\).
- else:
- Test whether crown area of \(t_i\)
intersects any other tree crown area.
- if True: discard \(t_i\)
- if False: keep \(t_i\)
# Create canopy filter
canopy.filter.b <- unlist(lapply(1:nrow(stem.buff), \(i) {
if(stem.buff[i,]$Zpred < quantile(stem.buff$Zpred, .90)) {
if(length(stem.within[[i]])<=1) {
if(length(stem.overlap[[i]])) {
tst <- 0
} else {
tst <- 1
}
} else {
tst <- 0
}
} else {
tst <- 1
}
as.logical(tst)
}
))
# Apply canopy filter
stem.filt.b <- stem.buff[canopy.filter.b,]
Map

Confusion matrix
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
65 |
24 |
183 |
0 |
0 |
| PICO |
0 |
6 |
1 |
0 |
0 |
| PIEN |
20 |
2 |
181 |
0 |
0 |
| POTR |
0 |
1 |
1 |
0 |
0 |
| UNKN |
0 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.18 |
0.52 |
0.48 |
0.57 |
| Class |
Sensitivity |
Specificity |
Pos.Pred.Value |
Neg.Pred.Value |
Precision |
Recall |
F1 |
Prevalence |
Detection.Rate |
Detection.Prevalence |
Balanced.Accuracy |
| Class: ABLA |
0.76 |
0.48 |
0.24 |
0.91 |
0.24 |
0.76 |
0.36 |
0.18 |
0.13 |
0.56 |
0.62 |
| Class: PICO |
0.18 |
1.00 |
0.86 |
0.94 |
0.86 |
0.18 |
0.30 |
0.07 |
0.01 |
0.01 |
0.59 |
| Class: PIEN |
0.49 |
0.81 |
0.89 |
0.34 |
0.89 |
0.49 |
0.64 |
0.76 |
0.37 |
0.42 |
0.65 |
| Class: POTR |
NA |
1.00 |
NA |
NA |
0.00 |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
| Class: UNKN |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
Agreement: kernel density by species and height

Agreement: frequency by species and size bin

4. Height-dependent buffer and filter on 90th percentile height
Use a height-dependent buffer and keep only trees in the 90th
percentile of height or higher.
Map

Confusion matrix
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
7 |
6 |
41 |
0 |
0 |
| PICO |
1 |
21 |
3 |
0 |
0 |
| PIEN |
7 |
7 |
126 |
0 |
0 |
| POTR |
0 |
0 |
0 |
0 |
0 |
| UNKN |
0 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.37 |
0.7 |
0.64 |
0.76 |
| Class |
Sensitivity |
Specificity |
Pos.Pred.Value |
Neg.Pred.Value |
Precision |
Recall |
F1 |
Prevalence |
Detection.Rate |
Detection.Prevalence |
Balanced.Accuracy |
| Class: ABLA |
0.47 |
0.77 |
0.13 |
0.95 |
0.13 |
0.47 |
0.20 |
0.07 |
0.03 |
0.25 |
0.62 |
| Class: PICO |
0.62 |
0.98 |
0.84 |
0.93 |
0.84 |
0.62 |
0.71 |
0.16 |
0.10 |
0.11 |
0.80 |
| Class: PIEN |
0.74 |
0.71 |
0.90 |
0.44 |
0.90 |
0.74 |
0.81 |
0.78 |
0.58 |
0.64 |
0.73 |
| Class: POTR |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
| Class: UNKN |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
Agreement: kernel density by species and height

Agreement: frequency by species and size bin

5. Static 3m buffer and filter on 90th percentile height
Apply a static 3px buffer around tree objects and keep only trees in
the 90th percentile of height or higher.
Map

Confusion matrix
## ABLA PICO PIEN POTR UNKN NA's
## 47 9 370 0 0 2
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
20 |
0 |
52 |
0 |
0 |
| PICO |
0 |
0 |
1 |
0 |
0 |
| PIEN |
4 |
3 |
120 |
0 |
0 |
| POTR |
0 |
0 |
0 |
0 |
0 |
| UNKN |
0 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.26 |
0.7 |
0.63 |
0.76 |
| Class |
Sensitivity |
Specificity |
Pos.Pred.Value |
Neg.Pred.Value |
Precision |
Recall |
F1 |
Prevalence |
Detection.Rate |
Detection.Prevalence |
Balanced.Accuracy |
| Class: ABLA |
0.83 |
0.70 |
0.28 |
0.97 |
0.28 |
0.83 |
0.42 |
0.12 |
0.1 |
0.36 |
0.77 |
| Class: PICO |
0.00 |
0.99 |
0.00 |
0.98 |
0.00 |
0.00 |
NaN |
0.01 |
0.0 |
0.00 |
0.50 |
| Class: PIEN |
0.69 |
0.74 |
0.94 |
0.27 |
0.94 |
0.69 |
0.80 |
0.86 |
0.6 |
0.64 |
0.72 |
| Class: POTR |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.0 |
0.00 |
NA |
| Class: UNKN |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.0 |
0.00 |
NA |
Agreement: kernel density by species and height

Agreement: frequency by species and size bin

6. Segment tree-crown polygons and no filtering
Segment crowns and keep all of them.
Maps
CHM with all segmented crowns

Species with all segmented crowns

Confusion matrix
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
293 |
107 |
533 |
0 |
0 |
| PICO |
8 |
150 |
10 |
0 |
0 |
| PIEN |
89 |
39 |
401 |
0 |
0 |
| POTR |
1 |
3 |
7 |
0 |
0 |
| UNKN |
0 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.26 |
0.51 |
0.49 |
0.54 |
| Class |
Sensitivity |
Specificity |
Pos.Pred.Value |
Neg.Pred.Value |
Precision |
Recall |
F1 |
Prevalence |
Detection.Rate |
Detection.Prevalence |
Balanced.Accuracy |
| Class: ABLA |
0.75 |
0.49 |
0.31 |
0.86 |
0.31 |
0.75 |
0.44 |
0.24 |
0.18 |
0.57 |
0.62 |
| Class: PICO |
0.50 |
0.99 |
0.89 |
0.90 |
0.89 |
0.50 |
0.64 |
0.18 |
0.09 |
0.10 |
0.74 |
| Class: PIEN |
0.42 |
0.81 |
0.76 |
0.51 |
0.76 |
0.42 |
0.54 |
0.58 |
0.24 |
0.32 |
0.62 |
| Class: POTR |
NA |
0.99 |
NA |
NA |
0.00 |
NA |
NA |
0.00 |
0.00 |
0.01 |
NA |
| Class: UNKN |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
Agreement: kernel density by species and height

Agreement: frequency by species and size bin

7. Segment tree-crown polygons and filter on 90th percentile
height
Segment crowns but use only trees in the 90th percentile of height or
higher.
Maps
CHM with all segmented crowns

Species with segmented crowns post-height filtering

Confusion matrix
|
Classification
|
| ABLA |
PICO |
PIEN |
POTR |
UNKN |
| Reference |
ABLA |
8 |
2 |
27 |
0 |
0 |
| PICO |
1 |
20 |
2 |
0 |
0 |
| PIEN |
8 |
9 |
103 |
0 |
0 |
| POTR |
0 |
0 |
0 |
0 |
0 |
| UNKN |
0 |
0 |
0 |
0 |
0 |
| Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| 0.42 |
0.73 |
0.66 |
0.79 |
| Class |
Sensitivity |
Specificity |
Pos.Pred.Value |
Neg.Pred.Value |
Precision |
Recall |
F1 |
Prevalence |
Detection.Rate |
Detection.Prevalence |
Balanced.Accuracy |
| Class: ABLA |
0.47 |
0.82 |
0.22 |
0.94 |
0.22 |
0.47 |
0.30 |
0.09 |
0.04 |
0.21 |
0.65 |
| Class: PICO |
0.65 |
0.98 |
0.87 |
0.93 |
0.87 |
0.65 |
0.74 |
0.17 |
0.11 |
0.13 |
0.81 |
| Class: PIEN |
0.78 |
0.65 |
0.86 |
0.52 |
0.86 |
0.78 |
0.82 |
0.73 |
0.57 |
0.67 |
0.71 |
| Class: POTR |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
| Class: UNKN |
NA |
1.00 |
NA |
NA |
NA |
NA |
NA |
0.00 |
0.00 |
0.00 |
NA |
Agreement: kernel density by species and height

Agreement: frequency by species and size bin

Compare accuracy
| Run |
Kappa |
Accuracy |
AccuracyLower |
AccuracyUpper |
| Ht-Dep Buffer - Least Conserv |
0.24 |
0.50 |
0.48 |
0.52 |
| Ht-Dep Buffer - Less Conserv |
0.31 |
0.56 |
0.52 |
0.60 |
| Ht-Dep Buffer - More Conserv |
0.18 |
0.52 |
0.48 |
0.57 |
| Ht-Dep Buffer - Most Conserv |
0.37 |
0.70 |
0.64 |
0.76 |
| Static Buffer - Most Conserv |
0.26 |
0.70 |
0.63 |
0.76 |
| Segmented Trees - Least Conserv |
0.26 |
0.51 |
0.49 |
0.54 |
| Segmented Trees - Most Conserv |
0.42 |
0.73 |
0.66 |
0.79 |